﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml.Linq;
using Phoenix.Xna.Framework.Serialization.Markup.Fields;
using Phoenix.Xna.Framework.Serialization.Markup.Properties;
using Phoenix.Xna.Framework.Xml;

namespace Phoenix.Xna.Framework.Serialization.Markup.Objects
{
    /// <summary>
    /// The intermediary markup object for non value types.
    /// </summary>
    internal class ClassMarkup : MarkupObjectBase
    {
        private List<Type> _genericArguments = new List<Type>();

        /// <summary>
        /// Gets all properties that are not set to default values.
        /// </summary>
        public List<IMarkupProperty> Properties { get; private set; }

        /// <summary>
        /// Gets all fields that are not set to default values.
        /// </summary>
        public List<IMarkupField> Fields { get; private set; }

        /// <summary>
        /// Gets or sets the reference ID for this markup; it is only set if two markupobjects share reference to this markup.
        /// </summary>
        public string ReferenceID { get; set; }

        /// <summary>
        /// Initializes a new instance of the ClassMarkup class.
        /// </summary>
        public ClassMarkup()
        {
            Properties = new List<IMarkupProperty>();
            Fields = new List<IMarkupField>();
        }

        private void AddAsProperty(PropertyInfo property, XElement propertyValue)
        {
            IMarkupProperty markupPropery = Serializer.MarkupSelector.GetMarkupPropertyFor(property);
            Properties.Add(markupPropery);
            markupPropery.Name = property.Name;
            markupPropery.Deserialize(propertyValue);
        }

        private void AddAsField(FieldInfo field, XElement fieldValue)
        {
            IMarkupField markupField = Serializer.MarkupSelector.GetMarkupFieldFor(field);
            Fields.Add(markupField);
            markupField.Name = field.Name;
            markupField.Deserialize(fieldValue);
        }

        #region IMarkupElement Members

        /// <summary>
        /// Serialize the element to XML.
        /// </summary>
        /// <returns>The element markup serialzed as an XElement.</returns>
        public override XElement Serialize()
        {
            XElement element = new XElement(NamespaceCache.GetNamespaceFor(ObjectType) + ObjectType.Name.Replace('`', '-'));

            if (!string.IsNullOrEmpty(ReferenceID))
            {
                element.Add(new XAttribute(AttributeIDs.ReferenceIDAttribute, ReferenceID));
            }

            foreach (Type t in _genericArguments)
            {
                XElement genericType = new XElement(NamespaceCache.GetNamespaceFor(t) + t.Name.Replace('`', '-'));
                genericType.SetAttribute(AttributeIDs.IsGenericType, true);
                element.Add(genericType);
            }

            foreach (IMarkupField markupField in Fields)
            {
                element.Add(markupField.Serialize());
            }

            foreach (IMarkupProperty markupProperty in Properties)
            {
                element.Add(markupProperty.Serialize());
            }

            return element;
        }

        /// <summary>
        /// Deserialzie the element from an XElement.
        /// </summary>
        /// <param name="xml">The XElement to deserialize this element from.</param>
        public override void Deserialize(XElement xml)
        {
            XElement element = (XElement)xml;

            foreach (XAttribute xmlAttribute in element.Attributes())
            {
                string name = xmlAttribute.Name.ToString();

                switch (name)
                {
                    case AttributeIDs.ReferenceIDAttribute:
                        ReferenceID = xmlAttribute.Value;
                        Serializer.ReferenceManager.AddObject(ReferenceID, this);
                        break;
                }
            }

            foreach (XElement xmlProperty in element.Nodes())
            {
                if (xmlProperty.ReadAttribute<bool>(AttributeIDs.IsGenericType))
                {
                    _genericArguments.Add(NamespaceCache.GetType(xmlProperty.Name.LocalName.Replace('-', '`'), xmlProperty.Name.Namespace));
                }
                else if (xmlProperty.ReadAttribute<bool>(AttributeIDs.IsFieldAttribute))
                {
                    string fieldID = xmlProperty.ReadAttribute<string>(AttributeIDs.FieldIDAttribute);

                    if (!string.IsNullOrEmpty(fieldID))
                    {
                        FieldInfo field = ObjectType.GetField(fieldID, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);

                        if (field != null)
                        {
                            AddAsField(field, xmlProperty);
                        }
                    }
                }
                else
                {
                    string propertyID = xmlProperty.ReadAttribute<string>(AttributeIDs.PropertyIDAttribute);

                    if (!string.IsNullOrEmpty(propertyID))
                    {
                        PropertyInfo property = ObjectType.GetProperty(propertyID, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);

                        if (property != null)
                        {
                            AddAsProperty(property, xmlProperty);
                        }
                    }
                }
            }
        }

        #endregion

        #region IMarkupObject Members

        /// <summary>
        /// Convert this MarkupObject in to an object model and set the Instance.
        /// </summary>
        public override void ConstructInstance()
        {
            if (ObjectType.IsGenericType)
            {
                Type constructedType = ObjectType.MakeGenericType(_genericArguments.ToArray());
                Instance = Activator.CreateInstance(constructedType);
            }
            else
            {
                Instance = Activator.CreateInstance(ObjectType);
            }

            foreach (IMarkupField markupField in Fields)
            {
                markupField.SetValue(Instance);
            }

            foreach (IMarkupProperty markupProperty in Properties)
            {
                markupProperty.SetValue(Instance);
            }
        }

        /// <summary>
        /// Extract the markup information from the specified object.
        /// </summary>
        /// <param name="instance">The instance to extract markup information for.</param>
        public override void ExtractMarkup(object instance)
        {
            Instance = instance;
            ObjectType = instance.GetType();

            if (ObjectType.IsGenericType)
            {
                Type[] types = ObjectType.GetGenericArguments();

                for (int i = 0; i < types.Length; i++)
                {
                    if (!types[i].IsGenericParameter)
                    {
                        _genericArguments.Add(types[i]);
                    }
                }
            }

            foreach (FieldInfo field in ObjectType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
            {
                IMarkupField markupField = Serializer.MarkupSelector.GetMarkupFieldFor(field);

                if (markupField != null)
                {
                    markupField.ExtractMarkup(field, field.GetValue(instance));

                    if (markupField.ShouldPersist)
                    {
                        Fields.Add(markupField);
                    }
                }
            }

            foreach (PropertyInfo property in ObjectType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
            {
                IMarkupProperty markupProperty = Serializer.MarkupSelector.GetMarkupPropertyFor(property);

                if (markupProperty != null)
                {
                    markupProperty.ExtractMarkup(property, property.GetValue(instance, null));

                    if (markupProperty.ShouldPersist)
                    {
                        Properties.Add(markupProperty);
                    }
                }
            }
        }

        #endregion
    }
}